home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / C / Frameworks / Hsoi's App Shell 1.0a4 / Hsoi's App Shell Source / HASFiles.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-01-28  |  30.4 KB  |  1,125 lines  |  [TEXT/CWIE]

  1. /*
  2.     HASFiles.c from Hsoi's App Shell © 1995-1997 John C. Daub.  All rights reserved.
  3.  
  4.     HASFiles.c deals with a lot of the "upper level" file handling functions...the
  5.     related menu commands, read/write, etc.  You can also find some other file related
  6.     utility functions in HASUtilFiles.c
  7.     
  8. */
  9.  
  10. /*    
  11.     question:
  12.     
  13.     Also, after we open a file, should we close it?  For some things that don't really
  14.     matter (like dragging a file and reading in it's contents, sure, close it afterwards).
  15.     But for things like opening a file that we're gonna edit, should we close it?  Other
  16.     people could always end up opening it (like if file shared), and that could cause all
  17.     sorts of problems...hmm
  18.     
  19. */
  20.  
  21. #pragma mark ••• #includes •••
  22.  
  23. #ifndef _WASTE_
  24. #include "WASTE.h"
  25. #endif
  26. #include "HASGlobals.h"
  27. #ifndef __HSOIS_APP_SHELL__
  28. #include "HASMain.h"
  29. #endif
  30. #include "HASDialogs.h"
  31. #include "HASFiles.h"
  32. #include "HASUtilCursors.h"
  33. #include "HASUtilFiles.h"
  34. #include "HASWindows.h"
  35. #include "HASUtilities.h"
  36. #include "HASUtilPStrings.h"
  37.  
  38. #include "WASTE_Objects.h"
  39.  
  40.  
  41. #pragma mark -
  42. #pragma mark ••• High Level Routines •••
  43.  
  44. /*
  45.  *    Simply enough, this is the function called when the user wants to open a document
  46.  *    via the File:Open (cmd-O) command
  47.  */
  48.  
  49. OSErr    HsoiDoOpen( void )
  50. {
  51.     StandardFileReply        reply;
  52.     SFTypeList                typeList;
  53.     OSErr                    retval = noErr;
  54.     Point                    where;
  55.     ModalFilterYDUPP        myGetFileFilter;
  56.     
  57.     // if we have a sound playing, stop it before we proceed
  58.     
  59.     if ( SoundIsPlaying() )
  60.         StopCurrentSound();
  61.     
  62.     // set up a list of file types we can open for GetFile
  63.     // in this case, all we can open are text files
  64.     
  65.     typeList[0] = TYPE_TEXT;
  66.     typeList[1] = TYPE_TEXT_READ_ONLY;
  67.     
  68.     
  69.     // if Point where is -1, -1, CustomGetFile will autocenter the dialog
  70.     
  71.     where.h = -1;
  72.     where.v = -1;
  73.     
  74.     // put up the standard open dialog box
  75.     // (we use CustomGetFile instead of StandardGetFile because we want to provide
  76.     // our own dialog filter procedure that takes care of updating our windows)
  77.     
  78.     // get our custom filter
  79.     
  80.     myGetFileFilter = NewModalFilterYDProc(hsoiMySFDialogFilter);
  81.     
  82.     // do the dialog
  83.     
  84.     CustomGetFile( nil, 2, typeList, &reply, 0, where, nil, myGetFileFilter, nil, nil, nil );
  85.     
  86.     // dump the filter
  87.     
  88.     DisposeRoutineDescriptor( myGetFileFilter );
  89.     
  90.     // if the user okayed the dialog, create a new window from the specified file
  91.     
  92.     if ( reply.sfGood )
  93.         retval = HsoiCreateWindow( &reply.sfFile );
  94.     else
  95.         retval = userCanceledErr;
  96.  
  97.     return retval;
  98. }
  99.  
  100. /*
  101.  *    this is the "lower level" function that deals with actually saving a window's
  102.  *    contents (this is called by DoSave and DoSaveAs)
  103.  */
  104.  
  105. OSErr    HsoiSaveWindow( const FSSpec *pFileSpec, WindowRef window )
  106. {
  107.     OSErr                err;
  108.     AliasHandle            aHandle;
  109.     DocumentHandle        hDocument;
  110.     
  111.     // get a handle to our window's document record
  112.     
  113.     hDocument = HsoiGetWindowDocument(window);
  114.     
  115.     // forget the existing handle cause we're gonna create a new one for it.
  116.     
  117.     HsoiForgetHandle( &(*hDocument)->fileAlias );
  118.     
  119.     // save text
  120.     
  121.     err = HsoiWriteTextFile( pFileSpec, false, (*hDocument)->we );
  122.     if ( err == noErr )
  123.     {
  124.         // set the window title to the file name
  125.         
  126.         SetWTitle( window, pFileSpec->name );
  127.         
  128.         // replace the old window alias (if any) with a new one created from pFileSpec
  129.         
  130.         HsoiForgetHandle( &(*hDocument)->fileAlias );
  131.         err = NewAlias( nil, pFileSpec, &aHandle );
  132.         // if err, aHandle will be nil, and it's not fatal, 
  133.         // just will make subsequent saves a pain
  134.         (*hDocument)->fileAlias = (Handle)aHandle;
  135.         
  136.         // incidentally, we could do the call to NewAlias and put the new alias in
  137.         // the document record in one line instead of 2...like this:
  138.         
  139.         // err = NewAlias( nil, pFileSpec, (AliasHandle *) &(*hDocument)->fileAlias);
  140.         
  141.         // but i just didn't want to do that cause of all the typecasting and stuff.
  142.         // typecasting, especially of this nature (with all the dereferencing, etc.)
  143.         // does generate better code (performance wise), but is not as readable.
  144.     }
  145.     
  146.     return err;
  147. }
  148.  
  149. /*
  150.  *    HsoiDoSaveAs() is called when the user selects "File:Save As" or selects "File:Save"
  151.  *    on a new, untitled document.  It prompts the user for a place to save it and does so
  152.  */
  153.  
  154.  
  155. OSErr    HsoiDoSaveAs( const FSSpec *suggestedTarget, WindowRef window )
  156. {
  157.     StringHandle        hPrompt;
  158.     Str255                defaultName;
  159.     StandardFileReply    reply;
  160.     OSErr                retval = noErr;
  161.     Point                where;
  162.     ModalFilterYDUPP    filterProc;
  163.  
  164.     // if we have a sound playing, stop it before we proceed
  165.     
  166.     if ( SoundIsPlaying() )
  167.         StopCurrentSound();
  168.  
  169.     // coordinates for where to place the putfile dialog...if -1,-1, the dialog will
  170.     // be autocentered on the screen
  171.     
  172.     where.v = -1;
  173.     where.h = -1;
  174.     
  175.     // get the prompt string for CustomPutFile from a 'STR ' resource and lock it
  176.     
  177.     hPrompt = GetString( strPutFilePrompt );
  178.     
  179.     // not thoroughly necesary to handle the error here...
  180.     
  181. //    if ( hPrompt == nil )
  182. //    {
  183. //        HsoiDoError( rErrorStrings, errFailGetResource, err, kNoteAlert );
  184. //        return err;
  185. //    }
  186.  
  187.     // lock the prompt down
  188.     
  189.     HLockHi( (Handle)hPrompt );
  190.     
  191.     // if a suggested target file is provided, use its name as the default name
  192.     
  193.     if ( suggestedTarget != nil )
  194.     {
  195.         HsoipStringCopy( suggestedTarget->name, defaultName );
  196.         HsoiSetDefaultDirectory( suggestedTarget );
  197.     }
  198.     else    //otherwise use the window title as the default name for CustomPutFile
  199.     {
  200.         GetWTitle( window, defaultName );
  201.     }
  202.     
  203.     // put up the standard save dialog box
  204.     
  205.     // get our dialog filter
  206.     
  207.     filterProc = NewModalFilterYDProc(hsoiMySFDialogFilter);
  208.     
  209.     // do the dialog
  210.     
  211.     CustomPutFile( *hPrompt, defaultName, &reply, 0, where, nil, filterProc, nil, nil, nil );
  212.     
  213.     // dump the filter
  214.     
  215.     DisposeRoutineDescriptor( filterProc );
  216.     
  217.     // unlock the string resource
  218.     
  219.     HUnlock( (Handle)hPrompt );
  220.     
  221.     // if the user okayed the dialog, save the window to he specified file
  222.     
  223.     if ( reply.sfGood )
  224.     {
  225.         retval = HsoiSaveWindow( &reply.sfFile, window );
  226.     }
  227.     else
  228.         retval = userCanceledErr;
  229.     
  230.     return retval;
  231. }
  232.  
  233.  
  234. /*
  235.  *    This is what's called if the user selects "File:Save".  if a new document, then
  236.  *    HsoiDoSaveAs is called to prompt for a new location, else HsoiSaveWindow is called
  237.  *    to easily and quickly save the file.
  238.  */
  239.  
  240. OSErr    HsoiDoSave( WindowRef window )
  241. {
  242.     FSSpec        spec;
  243.     FSSpecPtr    suggestedTarget = nil;
  244.     Boolean        promptForNewFile = true, aliasTargetWasChanged;
  245.     OSErr        retval = noErr;
  246.     
  247.     // resolve the alias associated with this window, if any
  248.     
  249.     if ( (*HsoiGetWindowDocument(window))->fileAlias != nil )
  250.     {
  251.         if ( ( ResolveAlias( nil, ((AliasHandle)(*HsoiGetWindowDocument(window))->fileAlias), 
  252.                                                 &spec, &aliasTargetWasChanged ) == noErr ) )
  253.         {
  254.             if ( aliasTargetWasChanged )
  255.                 suggestedTarget = &spec;
  256.             else
  257.                 promptForNewFile = false;
  258.         }
  259.     }
  260.     
  261.     // if no file has been previously associated with this window, or if the
  262.     // alias resolution has failed, or if the alias target was changed
  263.     // prompt the user for a new destination
  264.     
  265.     if ( promptForNewFile )
  266.         retval = HsoiDoSaveAs( suggestedTarget, window );
  267.     else
  268.     {
  269.         // if we have a sound playing, stop it before we proceed
  270.     
  271.         if ( SoundIsPlaying() )
  272.             StopCurrentSound();
  273.  
  274.         // save the file
  275.         
  276.         retval = HsoiSaveWindow( &spec, window );
  277.     }
  278.     
  279.     return retval;
  280. }
  281.  
  282. // DoRevert will revert the contents of the window (the "file") back to the most recent
  283. // saved version of the document.
  284.  
  285. // there is not really a need to check the window to make sure it's got a hWE instance
  286. // (i.e. not a dialog window, etc) cause in AdjustMenus, the only time Revert is Enabled
  287. // is if the window is a proper one.  Furthermore, due to this, we don't have to check
  288. // the vaildity of the AliasHandle in the DocumentRecord
  289.  
  290. OSErr    HsoiDoRevert( WindowRef theWindow )
  291. {
  292.      short                itemHit;
  293.      Str255                windowTitle;
  294.      FSSpec                targetFile;
  295.      Boolean                aliasChanged;
  296.      OSErr                err = noErr; // assume success
  297.  
  298.     // if we have a sound playing, stop it before we proceed
  299.     
  300.     if ( SoundIsPlaying() )
  301.         StopCurrentSound();
  302.      
  303.      // set the cursor to the arrow.  we call InitCursor() instead of like
  304.      // SetCursor( &qd.arrow ) cause InitCursor will make sure that if the cursor
  305.      // is hidden, it'll be shown.
  306.      
  307.     InitCursor();
  308.     
  309.     // get the title of the window for the Revert alert
  310.     
  311.     GetWTitle( theWindow, windowTitle );
  312.     
  313.     // set up the Alert with the window title
  314.     
  315.     ParamText( windowTitle, NIL_STRING, NIL_STRING, NIL_STRING );
  316.     
  317.      // do the alert
  318.      
  319.      itemHit = CautionAlert( rRevertAlert, HsoiGetMyStandardDialogFilter() );
  320.      
  321.     // if itemHit was the OK button (1), they want to revert...if !OK, don't revert
  322.     
  323.     if ( itemHit != ok )
  324.         return err;
  325.     else
  326.     {    
  327.         // resolve the alias to get the FSSpec
  328.         
  329.         err = ResolveAlias( nil, (AliasHandle)(*HsoiGetWindowDocument(theWindow))->fileAlias, &targetFile, &aliasChanged );
  330.         if ( err != noErr )
  331.         {
  332.             // there are a lot of different error codes that ResolveAlias returns...some fatal,
  333.             // some not...if you'd like to do more robust error handling, feel free to
  334.             
  335.             HsoiDoError( rErrorStrings, errFailResolveAlias, err, kErrStop );
  336.             
  337.             return err;
  338.         }
  339.         
  340.         // axe the old stuff
  341.         
  342.         HsoiDestroyWindow( theWindow );
  343.         
  344.         // create a new window with the "old" info in it
  345.         
  346.         err = HsoiCreateWindow( &targetFile );
  347.         if ( err != noErr )
  348.         {
  349.             HsoiDoError( rErrorStrings, errCantCreateWindow, err, kErrStop );            
  350.             return err;
  351.         }
  352.         
  353.     }    
  354.  
  355.     // made it this far?  we can return noErr
  356.     
  357.     return noErr;
  358. }
  359.  
  360.  
  361. #pragma mark -
  362. #pragma mark ••• Low Level Routines •••
  363.  
  364. /*
  365.  *    HsoiReadTextFile() takes care of actually opening up a file and reading it's
  366.  *    contents into a window
  367.  */
  368.  
  369.  
  370. OSErr    HsoiReadTextFile( const FSSpec *pFileSpec, WEReference we )
  371. {    
  372.     Size            textSize;
  373.     OSErr            err = noErr;
  374.     short            dataForkRefNum = 0;
  375.     short            resForkRefNum = 0;
  376.     Handle            hText = nil;
  377.     StScrpHandle    hStyles = nil;
  378.     WESoupHandle    hSoup = nil;
  379.     Boolean            stoppedSpinning = false;    // did we call HsoiStopVBLSpinning in error handling
  380.                                                 // or not?
  381.                                                 
  382.     // if we have a sound playing, stop it before we proceed.  hopefully
  383.     // the sound will have been stopped (if any) prior to here, but just in case
  384.     // things didn't work, we'll have this for added "security"
  385.     
  386.     if ( SoundIsPlaying() )
  387.         StopCurrentSound();
  388.  
  389.  
  390.     // since this might be a lengthy operation, let's spin the cursor with a VBL task
  391.     // as a note, with all our error handling, if we do hit an error, we'll first
  392.     // stop the spinning cursor the do the error.
  393.  
  394.     HsoiStartVBLSpinning();
  395.     
  396.     //    open the data fork with read-only permission
  397.     
  398.     err = FSpOpenDF( pFileSpec, fsRdPerm, &dataForkRefNum );
  399.     if ( err != noErr )
  400.     {
  401.         HsoiStopVBLSpinning();
  402.         stoppedSpinning = true;
  403.         HsoiDoError( rReadErrorStrings, strRdTxFSpOpenDF, err, kErrStop );
  404.         goto cleanup;
  405.     }
  406.     
  407.     //    get data fork size
  408.     
  409.     err = GetEOF( dataForkRefNum, &textSize );
  410.     if ( err != noErr )
  411.     {
  412.         HsoiStopVBLSpinning();
  413.         stoppedSpinning = true;
  414.         HsoiDoError( rReadErrorStrings, strRdTxGetEOF, err, kErrStop );
  415.         goto cleanup;
  416.     }
  417.  
  418.     // set the position in the file from where to start reading
  419.     
  420.     err = SetFPos( dataForkRefNum, fsFromStart, 0L );
  421.     if ( err != noErr )
  422.     {
  423.         HsoiStopVBLSpinning();
  424.         stoppedSpinning = true;
  425.         HsoiDoError( rReadErrorStrings, strRdTxSetFPos, err, kErrStop );
  426.         goto cleanup;
  427.     }
  428.     
  429.         
  430.     //    Try to allocate a handle that large, use temporary memory if available
  431.     
  432.     err = HsoiNewHandleTemp( textSize, &hText );
  433.     
  434.     if ( err || (StripAddress( *hText ) == 0L) )
  435.     {
  436.         HsoiStopVBLSpinning();
  437.         stoppedSpinning = true;    
  438.         HsoiDoError( rReadErrorStrings, strRdTxNewHandle, err, kErrStop );
  439.         goto cleanup;
  440.     }
  441.     
  442.     
  443.     //    read in the text
  444.     
  445.     HLock( hText );
  446.     err = FSRead( dataForkRefNum, &textSize, *hText );
  447.     HUnlock( hText );
  448.     if ( err != noErr )
  449.     {
  450.         HsoiStopVBLSpinning();
  451.         stoppedSpinning = true;
  452.         HsoiDoError( rReadErrorStrings, strRdTxFSRead, err, kErrStop );
  453.         goto cleanup;
  454.     }
  455.     
  456.     // let's close the data fork before opening up the resource fork.
  457.     
  458.     err = FSClose( dataForkRefNum );
  459.     if ( err != noErr )
  460.     {
  461.         HsoiStopVBLSpinning();
  462.         stoppedSpinning = true;
  463.         HsoiDoError( rReadErrorStrings, strRdTxFSClose, err, kErrStop );
  464.         goto cleanup;
  465.     }
  466.     else
  467.         dataForkRefNum = 0;
  468.     
  469.     //    see if the file has a resource fork
  470.     
  471.     resForkRefNum = FSpOpenResFile( pFileSpec, fsCurPerm );
  472.     if ( resForkRefNum != -1 ) //FSpOpenResFile will return -1 if it fails
  473.     {
  474.         //    look for a style scrap resource (get the first one; the resource ID doesn't matter)
  475.         
  476.         //    this whole resource stuff is interesting.  first, we could have no resource fork
  477.         //    at all (and that's easy to deal with).  second, we could have a resource fork
  478.         //    and the resources we want ('styl' and 'SOUP').  but third, we could have
  479.         //     a resource fork but not the resources we want.  it's simple to deal with all these
  480.         //    things, but just make sure that you do!
  481.         
  482.         hStyles = (StScrpHandle)Get1IndResource( TYPE_STYLE, 1 );
  483.         err = ResError();
  484.         if ( (err != noErr) && (err != resNotFound) )
  485.         {
  486.             // if we have some strange, funky error
  487.             
  488.             HsoiStopVBLSpinning();
  489.             stoppedSpinning = true;
  490.             hStyles = nil;
  491.             HsoiDoError( rReadErrorStrings, strRdTxGetStyleH, err, kErrStop );
  492.             goto CRF;
  493.         }
  494.         else if ( err == resNotFound )
  495.         {
  496.             // have a resource fork, but just not a 'styl' resource
  497.             
  498.             hStyles = nil;
  499.         }
  500.         else
  501.             // got it!
  502.             DetachResource( (Handle)hStyles );
  503.             
  504.         //    look for a soup resource as well
  505.         hSoup = (WESoupHandle)Get1IndResource( TYPE_SOUP, 1 );
  506.         err = ResError();
  507.         if ( (err != noErr) && (err != resNotFound) )
  508.         {
  509.             // some funky error
  510.             
  511.             HsoiStopVBLSpinning();
  512.             stoppedSpinning = true;
  513.             hSoup = nil;
  514.             HsoiDoError( rReadErrorStrings, strRdTxGetSoupH, err, kErrStop );
  515.             goto CRF;
  516.         }
  517.         else if ( err == resNotFound )
  518.         {
  519.             // just no 'SOUP' resource
  520.             
  521.             hSoup = nil;
  522.         }
  523.         else
  524.             // got it!
  525.             
  526.             DetachResource( (Handle)hSoup );
  527.  
  528. CRF:        
  529.         CloseResFile( resForkRefNum );
  530.         err = ResError();
  531.         if ( err != noErr )
  532.         {
  533.             HsoiStopVBLSpinning();
  534.             stoppedSpinning = true;
  535.             HsoiDoError( rReadErrorStrings, strRdTxCloseResFile, err, kErrStop );
  536.             goto cleanup;
  537.         }
  538.         else
  539.             resForkRefNum = 0;
  540.  
  541.     }
  542.     else
  543.     {
  544.         // does not have resource fork, so no style or soup
  545.         
  546.         hStyles = nil;
  547.         hSoup = nil;
  548.     }
  549.     
  550.     
  551.     //    insert the text into the WE record
  552.     
  553.     // don't lock (HLock) the style and soup handles here....it's not
  554.     // really necessary to do, but also, there is a chance that the
  555.     // handles could be nil (no resource fork for the file).  passing
  556.     // nil to the Memory Manager can be a BIG problem.  Thanx to
  557.     // Chris Thomas <mailto:ckt@best.com> for the fix
  558.     
  559.     HLock( hText );
  560.     err = WEInsert( *hText, textSize, hStyles, hSoup, we );
  561.     HUnlock( hText );
  562.     
  563.     if ( err != noErr )
  564.     {
  565.         HsoiStopVBLSpinning();
  566.         stoppedSpinning = true;
  567.         HsoiDoError( rReadErrorStrings, strRdTxWEInsert, err, kErrStop );
  568.         goto cleanup;
  569.     }
  570.  
  571.     
  572.     //    set the insertion point at the beginning of the text
  573.     WESetSelection( 0, 0, we );
  574.     
  575.     //    reset the WE instance modification count
  576.     WEResetModCount( we );
  577.     
  578.     //    clean up and exit
  579.  
  580.     err = noErr; // if we made it this far, there were no errors
  581.     
  582. cleanup:
  583.     
  584.     // dump our handles so we don't hog nor fragment memory with stuff we don't
  585.     // need anymore
  586.     
  587.     HsoiForgetHandle(&hText);
  588.     HsoiForgetHandle((Handle *)&hStyles);
  589.     HsoiForgetHandle((Handle *)&hSoup);
  590.  
  591.     // close the file forks
  592.     
  593.     if ( dataForkRefNum > 0 )
  594.     {
  595.         FSClose(dataForkRefNum);
  596.         dataForkRefNum = 0;
  597.     }
  598.     
  599.     if ( resForkRefNum > 0 )
  600.     {
  601.         CloseResFile(resForkRefNum);
  602.         resForkRefNum = 0;
  603.     }
  604.  
  605.     // and stop spinning our cursor
  606.         
  607.     if ( !stoppedSpinning )
  608.         HsoiStopVBLSpinning();
  609.     
  610.     return err;
  611. }
  612.  
  613.  
  614. /*
  615.  *    HsoiWriteTextFile is my "low level" routine to handle the actual writing of data
  616.  *    from window to disk file.
  617.  *    
  618.  *    this contains many "proper" ways to save data to disk:  checking for locked files,
  619.  *    using temp files, using name strings, etc.  This is a very safe and recommended
  620.  *    way to save data to disk (to prevent sh*t from happening)
  621.  *
  622.  */
  623.  
  624. OSErr    HsoiWriteTextFile( const FSSpec *pFileSpec, Boolean isReadOnly, WEReference we )
  625. {
  626.     FInfo            fileInfo;
  627.     Size            textSize;
  628.     Boolean            replacing = false;
  629.     OSErr            err = noErr;
  630.     short            dataForkRefNum = 0;
  631.     short            resForkRefNum = 0;
  632.     Handle            hText = nil;
  633.     StScrpHandle    hStyles = nil;
  634.     WESoupHandle    hSoup = nil;    
  635.     Handle            strHandle;        // these 4 variables are used to copy the name
  636.     Str255            strName;        // string resource from the app's resource fork
  637.     ResType            strType;        // into the file's resource fork.  see below for
  638.     short            strID;            // more details.
  639.     unsigned long    theTime;
  640.     Str255            tempFileName;
  641.     FSSpec            tempFileSpec;
  642.     short            tempVRef;        // volume reference # for the temp file
  643.     long            tempDirID;        // directory ID of the temp file
  644.     Boolean            stoppedSpinning = false;
  645.  
  646.     // if we have a sound playing, stop it before we proceed.  hopefully, the
  647.     // sound (if any) was stopped before we got to here, but just in case,
  648.     // we'll have this here for added security.
  649.     
  650.     if ( SoundIsPlaying() )
  651.         StopCurrentSound();
  652.     
  653.     // this could take a while to do so let's spin the cursor with a VBL cursor
  654.  
  655.     HsoiStartVBLSpinning();
  656.     
  657.     //    will we be replacing an existing file?
  658.  
  659.     err = FSpGetFInfo( pFileSpec, &fileInfo );
  660.     if ( err == noErr )
  661.         replacing = true;
  662.     else if ( err == fnfErr )
  663.         replacing = false;
  664.     else
  665.     {
  666.         HsoiStopVBLSpinning();
  667.         stoppedSpinning = true;
  668.         HsoiDoError( rWriteErrorStrings, strWrTxFFSpGetInfo, err, kErrNote );
  669.         goto cleanup;
  670.     }
  671.     
  672.     // if the file currently exists (meaning it was saved at some time in the past, be it
  673.     // 5 minutes ago, or 5 days ago), there is a chance that it could be locked (from the
  674.     // Finder's "Get Info" window (or otherwise).  If the file is locked, you cannot
  675.     // save changes.  Therefore, if 'replacing' is true, we have to throw up a message
  676.     // saying that we can't do this, then exit.
  677.     
  678.     if ( replacing )
  679.     {
  680.         // check if the file is locked
  681.         
  682.         err = HsoiFSpCheckObjectLock( pFileSpec );
  683.         if ( err != noErr ) // if it returns noErr, the file/directory is NOT locked
  684.         {
  685.             short                itemHit;
  686.             Str255                errString;
  687.             
  688.             // stop our VBL cursor from spinning
  689.             
  690.             HsoiStopVBLSpinning();
  691.             stoppedSpinning = true;
  692.             
  693.             // give us an arrow cursor
  694.             
  695.             //SetCursor( &qd.arrow );
  696.             InitCursor();
  697.             
  698.             // change our error number to a string for ease of display
  699.             
  700.             NumToString( (long)err, errString );
  701.             
  702.             // get the text in the dialog set up
  703.             
  704.             ParamText( pFileSpec->name, errString, NIL_STRING, NIL_STRING );
  705.             
  706.             // and blab it out
  707.             
  708.             itemHit = StopAlert( rFileLockedAlert, HsoiGetMyStandardDialogFilter() );
  709.                         
  710.             return err;
  711.         }
  712.     }
  713.     
  714.     // the technique we'll take to save/write our data will be to use temporary files.
  715.     // this is a handy thing and recommended way to save data.  why?  well, the "usual"
  716.     // way to save would be to just write; if it's a new file, just do it.  but if
  717.     // we're like writing data to a file that already exists (like you're saving as
  718.     // you go along working), to just write the new data to the old file could prove
  719.     // to be disasterous.  What if during the write of the new data there was a fubar?
  720.     // your old, original file could be ruined and no way to recover old work!  not
  721.     // a good thing.  Sure, this method isn't failsafe (like when you do the actual
  722.     // exhanging of the files, something might go wrong), but in my opinion (and
  723.     // the opinion of Apple and other developers), this is the best thing you can
  724.     // do to ensure your data will be safe if something goes wrong in the write.
  725.     
  726.     // so, here's what we do.  if the file already exists (replacing == true), we'll
  727.     // write our new data to a temporary file (just a "scrap" thing that we don't
  728.     // care about and after we're done, who cares what happens to it).  Then, if
  729.     // the new write was successful, just switch the new file into the old file's
  730.     // place, and viola!  it's seemless and nice "behind the scenes" sorta stuff
  731.     // that your user will never know about, but will probably come to appreciate
  732.     // some day :)
  733.     
  734.     if ( replacing )
  735.     {
  736.     
  737.         // create the temporary file name.  the name doesn't have to make sense, just
  738.         // be unique.  incidentally, if you're worried that perhaps two or more running
  739.         // applications will call GetDateTime at the same time and then generate the
  740.         // same temp file name, well...it might happen, but it's pretty darn slim.
  741.         // if you're really worried (especially if you're working on an app that might
  742.         // be used in a networked environment), i'm sure you can think up more ways
  743.         // to ensure a totally 100% unique filename
  744.         
  745.         GetDateTime( &theTime );
  746.         NumToString( theTime, tempFileName );
  747.             
  748.         // find the temporary items folder on the file's volume; create it if necessary
  749.         // it is important that the temp folder (and the temp file) and the "original" target
  750.         // file be on the same volume; if not, FSpExchangeFiles will return diffVolErr (-1303)
  751.         // and won't work
  752.         
  753.         err = FindFolder( pFileSpec->vRefNum, kTemporaryFolderType, kCreateFolder, &tempVRef, &tempDirID );
  754.         if ( err != noErr )
  755.         {
  756.             HsoiStopVBLSpinning();
  757.             stoppedSpinning = true;
  758.             HsoiDoError( rWriteErrorStrings, strWrTxFindFolder, err, kErrStop );            
  759.             goto cleanup;        
  760.         }
  761.         
  762.         // make an FSSpec for the temp file
  763.         
  764.         err = FSMakeFSSpec( tempVRef, tempDirID, tempFileName, &tempFileSpec );
  765.         if ( (err != noErr) && (err != fnfErr ) )
  766.         {
  767.             HsoiStopVBLSpinning();
  768.             stoppedSpinning = true;
  769.             HsoiDoError( rWriteErrorStrings, strWrTxFSMakeFSSpec, err, kErrStop );            
  770.             goto cleanup;
  771.         }
  772.         
  773.     }
  774.  
  775.     //    create a new file.  if we're replacing, make a temp file.  if it's a
  776.     //  new file from the onset, just create the file
  777.     
  778.     // if the user wants to make this file a read-only file (a type 'ttro'), then
  779.     // we do that accordingly..else, just a regular 'TEXT' file.  Currently, the
  780.     // only place this is "relevant" is in the Help dialog when the user
  781.     // saves the help text (it will output a read-only file).  However, if you'd
  782.     // like to allow the user to save regular document files as TEXT or ttro,
  783.     // you'll have to go back to the CustomPutFile() call, put in a means to
  784.     // get this info from the user (maybe 2 radio buttons or a small popup menu),
  785.     // and then pass this info on to here.
  786.     
  787.     if ( replacing )
  788.         FSpCreateResFile( &tempFileSpec, 'trsh', 'trsh', smSystemScript );
  789.     else
  790.     {
  791.         if ( isReadOnly )
  792.             FSpCreateResFile( pFileSpec, APP_SIGNATURE, TYPE_TEXT_READ_ONLY, smSystemScript );
  793.         else
  794.             FSpCreateResFile( pFileSpec, APP_SIGNATURE, TYPE_TEXT, smSystemScript );
  795.     }
  796.     
  797.     err = ResError();
  798.     if ( err != noErr )
  799.     {
  800.         HsoiStopVBLSpinning();
  801.         stoppedSpinning = true;
  802.         HsoiDoError( rWriteErrorStrings, strWrTxFFSpCrRsF, err, kErrNote );
  803.         goto cleanup;
  804.     }
  805.     
  806.     //  if replacing an old file, copy the old file information
  807.     if ( replacing )
  808.     {
  809.         err = FSpSetFInfo( &tempFileSpec, &fileInfo );
  810.         if ( err != noErr )
  811.         {
  812.             HsoiStopVBLSpinning();
  813.             stoppedSpinning = true;
  814.             HsoiDoError( rWriteErrorStrings, strWrTxFFSpSetFInfo, err, kErrNote );
  815.             goto cleanup;
  816.         }
  817.     }
  818.     
  819.         
  820.     //    open the data fork for writing
  821.     if ( replacing )
  822.         err = FSpOpenDF( &tempFileSpec, fsCurPerm, &dataForkRefNum );
  823.     else
  824.         err = FSpOpenDF( pFileSpec, fsCurPerm, &dataForkRefNum );
  825.         
  826.     if ( err != noErr )
  827.     {
  828.         HsoiStopVBLSpinning();
  829.         stoppedSpinning = true;
  830.         HsoiDoError( rWriteErrorStrings, strWrTxFFSpOpenDF, err, kErrNote );
  831.         goto cleanup;
  832.     }
  833.     
  834.     //    set the end-of-file
  835.     
  836.     err = SetEOF( dataForkRefNum, nil );
  837.     if ( err != noErr )
  838.     {
  839.         HsoiStopVBLSpinning();
  840.         stoppedSpinning = true;
  841.         HsoiDoError( rWriteErrorStrings, strWrTxFSetEOF, err, kErrNote );
  842.         goto cleanup;
  843.     }
  844.     
  845.     //    set the position in the file to write from
  846.     
  847.     err = SetFPos( dataForkRefNum, fsFromStart, nil );
  848.     if ( err != noErr )
  849.     {
  850.         HsoiStopVBLSpinning();
  851.         stoppedSpinning = true;
  852.         HsoiDoError( rWriteErrorStrings, strWrTxFSetFPos, err, kErrNote );
  853.         goto cleanup;
  854.     }
  855.     
  856.     
  857.     
  858.     //    get the text handle from the WE instance
  859.     //    WEGetText returns the original handle, not a copy, so don't dispose of it!!
  860.     hText = WEGetText( we );
  861.     textSize = GetHandleSize( hText );
  862.     
  863.     //    write the text
  864.     HLock( hText );
  865.     err = FSWrite( dataForkRefNum, &textSize, *hText );    
  866.     HUnlock( hText );
  867.  
  868.     if ( err != noErr )
  869.     {
  870.         HsoiStopVBLSpinning();
  871.         stoppedSpinning = true;
  872.         HsoiDoError( rWriteErrorStrings, strWrTxFFSWrite, err, kErrNote );
  873.         goto cleanup;
  874.     }
  875.     
  876.     //    open the resource file for writing
  877.     
  878.     if ( replacing )
  879.         resForkRefNum = FSpOpenResFile( &tempFileSpec, fsRdWrPerm );
  880.     else
  881.         resForkRefNum = FSpOpenResFile( pFileSpec, fsRdWrPerm );
  882.         
  883.     err = ResError();
  884.     if ( err != noErr )
  885.     {
  886.         HsoiStopVBLSpinning();
  887.         stoppedSpinning = true;
  888.         HsoiDoError( rWriteErrorStrings, strWrTxFFSpOpenResFile, err, kErrNote );
  889.         goto cleanup;
  890.     }
  891.     
  892.     //    allocate temporary handles to hold the style scrap and the soup
  893.     //    try tapping temporary memory since WECopyRange() could get huge
  894.     
  895.     err = HsoiNewHandleTemp( 0, (Handle *)&hStyles );
  896.     if ( err != noErr )
  897.     {
  898.         HsoiStopVBLSpinning();
  899.         stoppedSpinning = true;
  900.         HsoiDoError( rWriteErrorStrings, strWrTxFstylMemErr, err, kErrNote );
  901.         goto cleanup;
  902.     }
  903.     
  904.     err = HsoiNewHandleTemp( 0, (Handle *)&hSoup );
  905.     if ( err != noErr )
  906.     {
  907.         HsoiStopVBLSpinning();
  908.         stoppedSpinning = true;
  909.         HsoiDoError( rWriteErrorStrings, strWrTxFsoupMemErr, err, kErrNote );
  910.         goto cleanup;
  911.     }
  912.     
  913.     //    create the style scrap and the soup
  914.     
  915.  
  916.     err = WECopyRange( 0, MAXLONG, nil, hStyles, hSoup, we );
  917.     if ( err != noErr )
  918.     {
  919.         HsoiStopVBLSpinning();
  920.         stoppedSpinning = true;
  921.         HsoiDoError( rWriteErrorStrings, strWrTxFWECopyRange, err, kErrNote );
  922.         goto cleanup;
  923.     }
  924.     
  925.     //    make them resource handles.  AddResource doesn't actually write the resource
  926.     // out to the file, it just converts a "regular old" handle to a handle that
  927.     // the Resource Manager can recognize (makes it into a resource handle).  FYI:
  928.     // the opposite of this (convert a resource handle to a regular handle) is
  929.     // DetachResource().  See IM:More Macintosh Toolbox (the Resource Manager stuff)
  930.     // for more information.
  931.     
  932.     AddResource( (Handle)hStyles, TYPE_STYLE, 128, NIL_STRING );
  933.     err = ResError();
  934.     if ( err != noErr )
  935.     {
  936.         HsoiStopVBLSpinning();
  937.         stoppedSpinning = true;
  938.         HsoiDoError( rWriteErrorStrings, strWrTxFstylAddRes, err, kErrNote );
  939.         goto cleanup;
  940.     }
  941.     
  942.     AddResource( (Handle)hSoup, TYPE_SOUP, 128, NIL_STRING );
  943.     err = ResError();
  944.     if ( err != noErr )
  945.     {
  946.         HsoiStopVBLSpinning();
  947.         stoppedSpinning = true;
  948.         HsoiDoError( rWriteErrorStrings, strWrTxFsoupAddRes, err, kErrNote );
  949.         goto cleanup;
  950.     }
  951.     
  952.     //    write them to the resource file
  953.     
  954.     HLock( (Handle)hStyles );
  955.     ChangedResource( (Handle)hStyles );
  956.     HUnlock( (Handle)hStyles );
  957.     WriteResource( (Handle)hStyles );
  958.     err = ResError();
  959.     if ( err != noErr )
  960.     {
  961.         HsoiStopVBLSpinning();
  962.         stoppedSpinning = true;
  963.         HsoiDoError( rWriteErrorStrings, strWrTxFstylWriteRes, err, kErrNote );
  964.         goto cleanup;
  965.     }
  966.     
  967.     HLock( (Handle)hSoup );
  968.     ChangedResource( (Handle)hSoup );
  969.     HUnlock( (Handle)hSoup );
  970.     WriteResource( (Handle)hSoup );
  971.     err = ResError();
  972.     if ( err != noErr )
  973.     {
  974.         HsoiStopVBLSpinning();
  975.         stoppedSpinning = true;
  976.         HsoiDoError( rWriteErrorStrings, strWrTxfsoupWriteRes, err, kErrNote );
  977.         goto cleanup;
  978.     }
  979.     
  980.     /*    Now, let's add the name string to the file.  This string ('STR ' -16396) contains
  981.         the name of the application.  If a file is "orphaned", the Finder will use this
  982.         string to notify you of the app the belongs to/was created by. Neat huh?
  983.         I wonder why more apps don't do this...
  984.         
  985.         One kicker here tho...we create 'TEXT' files...Teach/SimpleText can open these
  986.         files.  So, if your app goes bye-bye, the Finder will "default" to asking if
  987.         they want to open the file up in TeachText/SimpleText.  So, you won't get
  988.         your cool name string, but at least people can still read their text.
  989.         
  990.         The following is pretty much taken directly from IM: Toolbox Essentials, 7-29
  991.     */
  992.     
  993.     UseResFile( gAppResourceFork ); // switch the "current open rsrc fork" to that of the app
  994.     
  995.     strHandle = GetResource( 'STR ', strAppNameString ); // get the name string
  996.     
  997.     if ( strHandle != nil )
  998.     {
  999.         GetResInfo( strHandle, &strID, &strType, strName ); // get resource name
  1000.         DetachResource( strHandle );  // detach the resource
  1001.         
  1002.         UseResFile( resForkRefNum );  // swtich back to the file's resource fork.
  1003.         AddResource( strHandle, strType, strID, strName ); // add the resource
  1004.         
  1005.         if ( ResError() == noErr )
  1006.             WriteResource( strHandle ); // write the resource handle
  1007.         
  1008.     }
  1009.     //    "clean" this document by resetting the WE instance modification count
  1010.     
  1011.     WEResetModCount( we );
  1012.     
  1013.     //    clean up
  1014.     
  1015.     // if we made it this far, no errors to return
  1016.     
  1017.     err = noErr;
  1018.  
  1019. cleanup:
  1020.     
  1021.     // remember, don't dispose the hText handle!
  1022.     
  1023.     HsoiForgetResource( (Handle *)&hStyles );
  1024.     HsoiForgetResource( (Handle *)&hSoup );
  1025.     HsoiForgetResource( (Handle *)&strHandle );
  1026.     
  1027.     if ( dataForkRefNum > 0 )
  1028.     {
  1029.         FSClose( dataForkRefNum );
  1030.         dataForkRefNum = 0;
  1031.     }
  1032.     
  1033.     if ( resForkRefNum > 0 )
  1034.     {    
  1035.         CloseResFile( resForkRefNum );
  1036.         resForkRefNum = 0;
  1037.     }
  1038.     
  1039.     if ( replacing && ( err == noErr ) )
  1040.     {
  1041.         // update the disk with any unwritten data
  1042.         
  1043.         FlushVol( NIL_STRING, tempFileSpec.vRefNum );
  1044.  
  1045.         // since we were replacing an existing file, let's now swap the original
  1046.         // and the temp file.  let's hear it for safe saves.
  1047.         
  1048.         err = FSpExchangeFiles( &tempFileSpec, pFileSpec );
  1049.         if ( err != noErr )
  1050.         {
  1051.             // handle the error
  1052.             
  1053.             return err;
  1054.         }
  1055.         
  1056.         // can the temp file since we don't need it anymore
  1057.         
  1058.         err = FSpDelete( &tempFileSpec );
  1059.         if ( err != noErr )
  1060.         {
  1061.             // handle the error
  1062.             
  1063.             return err;
  1064.         }
  1065.     }
  1066.     
  1067.     // and update the disk with any unwritten data
  1068.     
  1069.     if ( err == noErr )
  1070.         FlushVol( NIL_STRING, pFileSpec->vRefNum );
  1071.  
  1072.     if ( !stoppedSpinning )
  1073.         HsoiStopVBLSpinning();
  1074.         
  1075.         
  1076.     return err;
  1077.     
  1078. }
  1079.  
  1080.  
  1081.  
  1082. /*    
  1083.     HsoiTranslateDrag() is called when a file is dragged over an open application window (i.e
  1084.     a file's icon in the Finder is dragged over a window in your application).
  1085.     
  1086.     Now, I used to have it that if you dragged a file of type 'TEXT' over the window, this
  1087.     routine would open up the file and read it in.  If it was a file of not-TEXT, then it
  1088.     was just ignored.
  1089.     
  1090.     Then one day, Michael Kamprath's WASTE Object Handlers started to work...the hfs stuff
  1091.     started to take over, i still don't know why.  So now, whenever something of an hfs
  1092.     type (a Finder icon, be it a file/document, or a disk or whatever) is dragged over an
  1093.     application window, some FSSpec info about that is saved...if you double click it, it'll
  1094.     open whatever you stored in here.
  1095.     
  1096.     But, i would still like it that if you dragged a text file over the window, to hvae the
  1097.     file opened up and inserted.  So, what i've done is this:  i've commented out most
  1098.     of the stuff below (left in just enough stuff to ensure Kamprath's stuff will kick in)
  1099.     and then put some TEXT file opening stuff in HsoiMyRecieveHandler().
  1100.     
  1101.     See that function and also Kamprath's example code in his archive for more info
  1102. */
  1103.  
  1104. pascal OSErr HsoiTranslateDrag(DragReference theDrag, ItemReference theItem, FlavorType requestedType, Handle dataHandle)
  1105. {
  1106. #pragma unused ( theDrag, theItem, dataHandle )
  1107.  
  1108.     OSErr                err = badDragFlavorErr;    // assume failure
  1109.         
  1110.     // we'll try to translate HFS objects into TEXT, so make sure that is the requested type
  1111.  
  1112.     if ( requestedType != TYPE_TEXT )
  1113.         return err;
  1114.     
  1115.     if ( requestedType != TYPE_TEXT_READ_ONLY )
  1116.         return err;
  1117.         
  1118.     err = noErr;
  1119.     
  1120.     return err;
  1121.  
  1122. }
  1123.  
  1124.  
  1125.